﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using oSoft.Utilities;

namespace oSoft.ColorPicker
{
    public class oSoftColor
    {
        protected double _Red, _Green, _Blue, _Hue, _Sat, _Lum;
        protected Color _Color;

        /// <summary>
        /// Creates an oSoftColor object from Hue, Saturation and Luminosity
        /// </summary>
        /// <param name="Hue">Hue, in the range from 0.0 to 1.0</param>
        /// <param name="Sat">Saturation, in the range from 0.0 to 1.0</param>
        /// <param name="Lum">Luminosity, in the range from 0.0 to 1.0</param>
        /// <returns></returns>
        public static oSoftColor FromHSL(double Hue, double Sat, double Lum)
        {
            System.Text.StringBuilder sb = new StringBuilder();

            if (Sat < 0.0 || Sat > 1.0)
                sb.Append(String.Format("Saturation must be between 0.0 and 1.0"));

            if (Lum < 0.0 || Lum > 1.0)
                sb.Append(String.Format("Luminosity must be between 0.0 and 1.0"));

            if (Hue < 0.0 || Hue > 1.0)
                sb.Append("Hue must be between 0 and 1.0");

            if (sb.Length > 0) throw new System.ArgumentException(sb.ToString());

            oSoftColor c = new oSoftColor();

            c._Hue = Hue;
            c._Sat = Sat;
            c._Lum = Lum;

            c.setRGBFromHSL();
            c.setColorFromRGB();

            return c;
        }

        /// <summary>
        /// Creates an oSoftColor object from Red, Green and Blue components.
        /// </summary>
        /// <param name="Red">Red component, in the range from 0.0 to 1.0</param>
        /// <param name="Green">Green component, in the range from 0.0 to 1.0</param>
        /// <param name="Blue">Blue component, in the range from 0.0 to 1.0</param>
        /// <returns></returns>
        public static oSoftColor FromRGB(double Red, double Green, double Blue)
        {
            oSoftColor c = new oSoftColor();

            c._Red = Red;
            c._Green = Green;
            c._Blue = Blue;

            c.setHSLfromRGB();
            c.setColorFromRGB();

            return c;
        }

        /// <summary>
        /// Creates an oSoftColor object from Red, Green and Blue values.
        /// </summary>
        /// <param name="R">Red value, must be between 0 and 255</param>
        /// <param name="G">Green value, must be between 0 and 255</param>
        /// <param name="B">Blue value, must be between 0 and 255</param>
        /// <returns></returns>
        public static oSoftColor FromRGB(int R, int G, int B)
        {
            oSoftColor c = new oSoftColor();
            c.Color = Color.FromArgb(R, G, B);
            return c;
        }


        protected void setFromColor()
        {

            this._Red = _Color.R == 0 ? 0 : (_Color.R + 1) / 256.0d;
            this._Green = _Color.G == 0 ? 0 : (_Color.G + 1) / 256.0d;
            this._Blue = _Color.B == 0 ? 0 : (_Color.B + 1) / 256.0d;

            this.setHSLfromRGB();

        }

        /// <summary>
        /// Sets or Gets the Red component in the range from 0.0 to 1.0
        /// </summary>
        public double Red
        {
            set
            {
                if (value < 0.0 || value > 1.0)
                    throw new ArgumentException(String.Format("Red component must be between 0.0 and 1.0"));

                _Red = value;
                setHSLfromRGB();
                setColorFromRGB();
            }
            get { return _Red; }
        }

        /// <summary>
        /// Sets or Gets the Green component in the range from 0.0 to 1.0
        /// </summary>
        public double Green
        {
            set
            {
                if (value < 0.0 || value > 1.0)
                    throw new ArgumentException(String.Format("Green component must be between 0.0 and 1.0"));

                _Green = value;
                setHSLfromRGB();
                setColorFromRGB();
            }
            get { return _Green; }
        }

        /// <summary>
        /// Sets or Gets the Blue component in the range from 0.0 to 1.0
        /// </summary>
        public double Blue
        {
            set
            {
                if (value < 0.0 || value > 1.0)
                    throw new ArgumentException(String.Format("Blue component must be between 0.0 and 1.0"));

                _Blue = value;
                setHSLfromRGB();
                setColorFromRGB();
            }
            get { return _Blue; }
        }

        /// <summary>
        /// Sets or Gets the Hue value in the range from 0.0 to 1.0
        /// </summary>
        public double Hue
        {
            set
            {
                if (value < 0.0 || value > 1.0)
                    throw new ArgumentException(String.Format("Hue must be between 0.0 and 1.0"));

                _Hue = value;
                setRGBFromHSL();
                setColorFromRGB();
            }
            get { return _Hue; }
        }

        /// <summary>
        /// Sets or Gets the Saturation in the range from 0.0 to 1.0
        /// </summary>
        public double Sat
        {
            set
            {
                if (value < 0.0 || value > 1.0)
                    throw new ArgumentException(String.Format("Saturation must be between 0.0 and 1.0"));

                _Sat = value;
                setRGBFromHSL();
                setColorFromRGB();
            }
            get { return _Sat; }
        }

        /// <summary>
        /// Sets or Gets the Luminosity component in the range from 0.0 to 1.0
        /// </summary>
        public double Lum
        {
            set
            {
                if (value < 0.0 || value > 1.0)
                    throw new ArgumentException(String.Format("Luminosity must be between 0.0 and 1.0"));

                _Lum = value;
                setRGBFromHSL();
                setColorFromRGB();
            }
            get { return _Lum; }
        }

        /// <summary>
        /// Sets or Gets the Red value in the range from 0 to 255
        /// </summary>
        public int R
        {

            get
            {
                return (int)(_Red * 255);
            }
            set
            {
                if (value < 0 || value > 255)
                    throw new ArgumentException(String.Format("Red value must be between 0 and 255"));

                Red = (value == 0) ? 0.0 : (double) (value + 1) / 256.0;
            }
        }

        /// <summary>
        /// Sets or Gets the Greem value in the range from 0 to 255
        /// </summary>
        public int G
        {
            get
            {
                return (int)(_Green * 255);
            }
            set
            {
                if (value < 0 || value > 255)
                    throw new ArgumentException(String.Format("Green value must be between 0 and 255"));
                Green = (value == 0) ? 0.0 : (double) (value + 1) / 256.0;
            }
        }

        /// <summary>
        /// Sets or Gets the Blue value in the range from 0 to 255
        /// </summary>
        public int B
        {
            get
            {
                return (int)(_Blue * 255);
            }
            set
            {
                if (value < 0 || value > 255)
                    throw new ArgumentException(String.Format("Blue value must be between 0 and 255"));

                Blue = (value == 0) ? 0.0 : (double) (value + 1) / 256.0d;
            }
        }

        /// <summary>
        /// Sets or Gets the Hue value in the range from 0º to 360º
        /// </summary>
        public int H
        {
            set
            {
                if (value < 0 || value > 360)
                    throw new ArgumentException(String.Format("Hue value must be between 0 and 255"));
                Hue = (double)value / 360.0d;
            }

            get
            {
                return (int)(_Hue * 360);
            }
        }

        /// <summary>
        /// Sets or Gets the Saturation value in the range from 0 to 255
        /// </summary>
        public int S
        {
            set
            {
                if (value < 0 || value > 255)
                    throw new ArgumentException(String.Format("Saturation value must be between 0 and 255"));

                Sat = (double)value / 255.0d;
            }
            get
            {
                return (int)(_Sat * 255);
            }
        }

        /// <summary>
        /// Sets or Gets the Luminosity value in the range from 0 to 255
        /// </summary>
        public int L
        {
            set
            {
                if (value < 0 || value > 255)
                    throw new ArgumentException(String.Format("Luminosity value must be between 0 and 255"));
                Lum = (double)value / 255.0d;
            }
            get
            {
                return (int)(_Lum * 255);
            }
        }

        /// <summary>
        /// Sets or Gets the Color from System.Drawing.Color
        /// </summary>
        public Color Color
        {
            set
            {
                _Color = value;
                setFromColor();
            }
            get
            {
                return _Color;
            }
        }


        protected void setHSLfromRGB()
        {
            double v;
            double m;
            double vm;
            double r2, g2, b2;
            double h;
            double s;
            double l;

            h = 0; // default to black
            s = 0;
            l = 0;

            v = GenericMaxMin<double>.max(_Red, _Green, _Blue);
            m = GenericMaxMin<double>.min(_Red, _Green, _Blue);

            l = (m + v) / 2.0;

            if (l < 0.0)
            {
                throw new ArgumentException("l is invalid");
            }

            vm = v - m;

            s = vm;

            if (s > 0.0)
            {
                s /= (l <= 0.5) ? (v + m) : (2.0 - v - m);

                r2 = (v - _Red) / vm;
                g2 = (v - _Green) / vm;
                b2 = (v - _Blue) / vm;

            }
            else
            {
                if (s < 0)
                {
                    throw new ArgumentException("s is invalid");
                }
                else
                {
                    s = 0;
                    r2 = 0;
                    g2 = 0;
                    b2 = 0;
                }
            }

            if (_Red == v)
            {
                h = (_Green == m ? 5.0 + b2 : 1.0 - g2);
            }

            else if (_Green == v)
            {
                h = (_Blue == m ? 1.0 + r2 : 3.0 - b2);
            }
            else
            {
                h = (_Red == m ? 3.0 + g2 : 5.0 - r2);
            }

            h /= 6.0;

            _Hue = l==0?0:h;
            _Sat = s;
            _Lum = l;
        }

        protected void setColorFromRGB()
        {
            _Color = Color.FromArgb(255, (int)(_Red * 255.0d), (int)(_Green * 255.0d), (int)(_Blue * 255));
        }

        protected void setRGBFromHSL()
        {
            System.Text.StringBuilder sb = new StringBuilder();

            if (_Sat < 0 || _Sat > 1)
                sb.Append("Saturation must be between 0 and 1. ");

            if (_Lum < 0 || _Lum > 1)
                sb.Append("Luminosity must be between 0 and 1. ");

            if (_Hue < 0 || _Hue > 1)
                sb.Append("Hue must be between 0 and 1. ");

            if (sb.Length > 0) throw new System.ArgumentException(sb.ToString());

            double r = 0, g = 0, b = 0;
            double temp1, temp2;

            if (_Lum == 0)
            {
                r = g = b = 0;
            }

            else
            {

                if (_Sat == 0)
                {
                    r = g = b = _Lum;
                }

                else
                {
                    //temp2 = ((_L <= 0.5) ? _L * (1.0 + _S) : _L + _S - (_L * _S));
                    if (_Lum <= 0.5)
                    {
                        temp2 = _Lum * (1.0d + _Sat);
                    }
                    else
                    {
                        temp2 = _Lum + _Sat - _Lum * _Sat;
                    }

                    temp1 = 2.0 * _Lum - temp2;

                    double[] temp3 = new double[] { _Hue + 1.0 / 3.0, _Hue, _Hue - 1.0 / 3.0 };
                    double[] clr = new double[] { 0, 0, 0 };
                    for (int i = 0; i < 3; i++)
                    {
                        if (temp3[i] < 0)
                            temp3[i] += 1.0;

                        if (temp3[i] > 1)
                            temp3[i] -= 1.0;

                        if (6.0 * temp3[i] < 1.0)
                            clr[i] = temp1 + (temp2 - temp1) * temp3[i] * 6.0;

                        else if (2.0 * temp3[i] < 1.0)
                            clr[i] = temp2;

                        else if (3.0 * temp3[i] < 2.0)
                            clr[i] = (temp1 + (temp2 - temp1) * ((2.0 / 3.0) - temp3[i]) * 6.0);
                        else
                            clr[i] = temp1;
                    }

                    _Red = clr[0];
                    _Green = clr[1];
                    _Blue = clr[2];
                }
            }
        }
    }
}
